title: ES6 - 读懂 ECMAScript 规格
date: 2020.12.13
top:

categories:

tags:


概述
术语
抽象操作的标准流程
相等运算符
数组的空位
数组的 map 方法

2020.12.13 星期日 16:

1 概述

规格文件是计算机语言的官方标准,详细描述语法规则和实现方法。

一般来说,没有必要阅读规格,除非你要写编译器。
但是,如果你遇到疑难的语法问题,实在找不到答案,这时可以去查看规格文件,了解语言标准是怎么说的。规格是解决问题的“最后一招”。

ECMAScript 6 的规格,可以在 ECMA 国际标准组织的官方网站(www.ecma-international.org/ecma-262/6.0/)免费下载和在线阅读。

ECMAScript 6 规格的 26 章之中,
第 1 章到第 3 章是对文件本身的介绍,与语言关系不大。
第 4 章是对这门语言总体设计的描述,有兴趣的读者可以读一下。
第 5 章到第 8 章是语言宏观层面的描述。第 5 章是规格的名词解释和写法的介绍,
第 6 章介绍数据类型,
第 7 章介绍语言内部用到的抽象操作,
第 8 章介绍代码如何运行。
第 9 章到第 26 章介绍具体的语法。

对于一般用户来说,除了第 4 章,其他章节都涉及某一方面的细节,不用通读,只要在用到的时候,查阅相关章节即可。

2 术语

抽象操作
所谓“抽象操作”(abstract operations)就是引擎的一些内部方法,外部不能调用。规格定义了一系列的抽象操作,规定了它们的行为,留给各种引擎自己去实现。

Record 和 field
ES6 规格将键值对(key-value map)的数据结构称为 Record,其中的每一组键值对称为 field。这就是说,一个 Record 由多个 field 组成,而每个 field 都包含一个键名(key)和一个键值(value)。

[[Notation]]
ES6 规格大量使用[[Notation]]这种书写法,比如[[Value]]、[[Writable]]、[[Get]]、[[Set]]等等。它用来指代 field 的键名。

Completion Record
每一个语句都会返回一个 Completion Record,表示运行结果。每个 Completion Record 有一个[[Type]]属性,表示运行结果的类型。
[[Type]]属性有五种可能的值。
normal
return
throw
break
continue

3 抽象操作的标准流程

抽象操作的运行流程,一般是下面这样。

  1. Let result be AbstractOp().
  2. If result is an abrupt completion, return result.
  3. Set result to result.[[Value]].
  4. return result.

4 相等运算符

0 == null

规格对每一种语法行为的描述,都分成两部分:先是总体的行为描述,然后是实现的算法细节。相等运算符的总体描述,只有一句话。
“The comparison x == y, where x and y are values, produces true or false.”
下面是算法细节。

由于0的类型是数值,null的类型是 Null(这是规格4.3.13 小节的规定,是内部 Type 运算的结果,跟typeof运算符无关)。
因此上面的前 11 步都得不到结果,要到第 12 步才能得到false。

5 数组的空位

const a1 = [undefined, undefined, undefined];
const a2 = [, , ,];

a1.length // 3
a2.length // 3

a1[0] // undefined
a2[0] // undefined

a1[0] === a2[0] // true

但是,它们实际上存在重大差异。

0 in a1 // true
0 in a2 // false

a1.hasOwnProperty(0) // true
a2.hasOwnProperty(0) // false

Object.keys(a1) // ["0", "1", "2"]
Object.keys(a2) // []

a1.map(n => 1) // [1, 1, 1]
a2.map(n => 1) // [, , ,]

“数组成员可以省略。只要逗号前面没有任何表达式,数组的length属性就会加 1,并且相应增加其后成员的位置索引。被省略的成员不会被定义。如果被省略的成员是数组最后一个成员,则不会导致数组length属性增加。”

6 数组的 map 方法

仔细查看上面的算法,可以发现,当处理一个全是空位的数组时,前面步骤都没有问题。进入第 10 步中第 2 步时,kPresent会报错,因为空位对应的属性名,对于数组来说是不存在的,因此就会返回,不会进行后面的步骤。

const arr = [, , ,];
arr.map(n => {
  console.log(n);
  return 1;
}) // [, , ,]

上面代码中,arr是一个全是空位的数组,map方法遍历成员时,发现是空位,就直接跳过,不会进入回调函数。因此,回调函数里面的console.log语句根本不会执行,整个map方法返回一个全是空位的新数组。